package com.moon.outer.mfs.web.common;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.security.Key;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.http.HttpRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.moon.outer.mfs.core.handler.DefaultAutoHandledException;
import com.moon.outer.mfs.core.handler.DefaultDescription;
import com.moon.outer.mfs.core.handler.DescriptionGroup;
import com.moon.outer.mfs.core.handler.ResponseDefinitionFactory;
import com.moon.outer.mfs.core.util.encryption.RSAUtil;
import com.moon.outer.mfs.pojo.base.BaseOperationRequest;
import com.moon.outer.mfs.pojo.base.BaseOperationResponse;

/**
 * 
 *************************************************
 * app加密解密拦截器
 *                                 
 * @version 0.0.1
 * @author  IccBoY                                                                 
 *************************************************
 */
public class SecurityInterceptor implements HandlerInterceptor {

	/**
	 * LOG
	 */
	private static final Logger LOG = LoggerFactory.getLogger(SecurityInterceptor.class);
	
	/** 
	 * <h2>解密/加密作用</h2>
	 * <ul>
	 * <li>使用了spring mvc的前置拦截器。客户端发送加了密的json字符串到服务端。
	 * 该方法负责将加密的json进行解密，得到明文的json重新构造目标方法参数的值，调用目标方法，
	 * 得到返回结果后，将起进行加密得到加密后的字符串，直接在写回客户端。</li>
	 * <li>
	 * 该方法过程就是在目标方法前后分别进行了处理，改变了原始请求和原始响应，而目标方法更多的关注的是具体的逻辑。
	 * 该思路可以运用到很多的场景，比如：1.解密加密场景；2.对象转换场景(服务端能处理的是json格式的数据，
	 * 客户端能处理的是xml，那就可以在这个拦截器里面进行json-xml的互转)。
	 * </li>
	 * <li>
	 * 这句代码值得关注：Object returnObj = handlerMethod.getMethod().invoke(handlerMethod.getBean(), invokeArgs.toArray());
	 * 改变了由spring mvc去调用目标方法，而在是拦截器的preHandle方法中 人为调用了一次，这样就灵活控制了输入和输出;
	 * 并且preHandle方法最终返回了false,表示不再执行后续的操作（因为在该方法中已经将该执行的都执行了）。
	 * </li>
	 * </ul>
	 * 
	 * @param request
	 * @param response
	 * @param handler
	 * @return
	 * @throws Exception
	 * @author IccBoY 2016-3-4 创建方法
	 */
	@Override
	public boolean preHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler) throws Exception {
	
			String url = request.getRequestURI();
			String body = getBody(request);
			Key key = null;
			// 获取请求过来的请求参数
			ResourceBundle rbIn = ResourceBundle.getBundle("app-keyin", Locale.US);
			String urlIn = rbIn.getString("in.url");
			ResourceBundle rbOut = ResourceBundle.getBundle("app-keyout", Locale.US);
			String urlOut = rbOut.getString("out.url");
			String bodyStr = null;
			ObjectInputStream ois = null;
			try {
				//获取classes文件路径 格式如/E:/workspaces/lms/target/test-classes/
				InputStream is = this.getClass().getResourceAsStream("/RSAPrikey.der");
				
				ois = new ObjectInputStream(is);
				// 私钥 解密加密 给服务器端使用
				key = (Key) ois.readObject();
			} catch (Exception e1) {
				BaseOperationResponse<String> baseOperationResponse = new BaseOperationResponse<String>(); 
				baseOperationResponse.setMessageCode("0");
				baseOperationResponse.setMessageDescription("获取RSAPrikey.der文件错误");
				throwAutoExption(baseOperationResponse, urlOut, url, response, key);
			}finally{
				if(null != ois){
					ois.close();
				}
			}
			 
			try {
				if (!urlIn.contains(url)) {
					// 解密后得到的字符串
					bodyStr = RSAUtil.decrypt(body, key, false);
				} else {
					bodyStr = body;
				}
			} catch (Exception e) {
				BaseOperationResponse<String> baseOperationResponse = new BaseOperationResponse<String>(); 
				baseOperationResponse.setMessageCode("0");
				baseOperationResponse.setMessageDescription("传入的参数错误");
				throwAutoExption(baseOperationResponse, urlOut, url, response, key);
			}

			if (handler instanceof HandlerMethod) {
				HandlerMethod handlerMethod = (HandlerMethod) handler;
				try {
					// new调用参数
					List<Object> invokeArgs = new ArrayList<Object>();
					// 传入参数类型
					Class<?>[] parameterTypes = handlerMethod.getMethod().getParameterTypes();
					for(int i=0 ; i<parameterTypes.length ; i++){
						// 如果是项目定义的通用请求参数对象BaseOperationRequest，解密
						if(parameterTypes[i].equals(BaseOperationRequest.class)){
							Type type = handlerMethod.getMethod().getGenericParameterTypes()[i];
							try{
								Object obj = JSON.parseObject(bodyStr, type);
								invokeArgs.add(obj);
							} catch (Exception e) {
								BaseOperationResponse<String> baseOperationResponse = new BaseOperationResponse<String>(); 
								baseOperationResponse.setMessageCode("0");
								baseOperationResponse.setMessageDescription("把json字符串解析为对象错误");
								throwAutoExption(baseOperationResponse, urlOut, url, response, key);
							}
						}else if(parameterTypes[i].equals(HttpServletRequest.class)){	// 如果是request,set HttpRequest
							invokeArgs.add(request);
						}else if(parameterTypes[i].equals(HttpSession.class)){	// 如果是session,set HttpSession
							invokeArgs.add(request.getSession());
						}
					}
					// 方法调用
					Object returnObj = handlerMethod.getMethod().invoke(
							handlerMethod.getBean(), invokeArgs.toArray());
					// 把返回对象转换成json字符串
					String jsonstr = JSONArray.toJSONString(returnObj, SerializerFeature.DisableCircularReferenceDetect);
					//System.out.println(jsonstr);
					String keyJssonStr = null;
					if (!urlOut.contains(url)) {
						// 把json字符串进行加密 传给客户端
						keyJssonStr = RSAUtil.encrypt(jsonstr, key);
					} else {
						keyJssonStr = jsonstr;
					}
					//System.out.println(keyJssonStr);
					response.setContentType("text/html");
					response.setCharacterEncoding("UTF-8");
					response.getWriter().write(keyJssonStr);
			
				}catch (InvocationTargetException exception) {
						Throwable e = exception.getTargetException();
						
						throwExption(e,urlOut,url,response,key);
						
				}catch (Exception e) {
					
					throwExption(e,urlOut,url,response,key);
				}
			}
		// 不执行后续KeyInterceptor的方法
		return false;
	}

	@Override
	public void postHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {

	}

	@Override
	public void afterCompletion(HttpServletRequest request,
			HttpServletResponse response, Object handler, Exception ex)
			throws Exception {

	}

	// 通过流的方式获得请求参数的内容
	public static String getBody(HttpServletRequest request) throws IOException {
		String body = null;
		StringBuilder stringBuilder = new StringBuilder();
		BufferedReader bufferedReader = null;
		try {
			InputStream inputStream = request.getInputStream();
			if (inputStream != null) {
				bufferedReader = new BufferedReader(new InputStreamReader(
						inputStream));
				char[] charBuffer = new char[128];
				int bytesRead = -1;
				while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
					stringBuilder.append(charBuffer, 0, bytesRead);
				}
			} else {
				stringBuilder.append("");
			}
		} catch (IOException ex) {
			throw ex;
		} finally {
			if (bufferedReader != null) {
				try {
					bufferedReader.close();
				} catch (IOException ex) {
					throw ex;
				}
			}
		}

		body = stringBuilder.toString();
		return body;
	}
	
	//封装Exception
	private void throwExption(Throwable e,String urlOut,String url,HttpServletResponse response,Key key) throws Exception{		
			DefaultAutoHandledException defaultAutoHandledException = null;
			//如果是自定义异常，则直接转换，否则作为统一的未知异常
			if (e instanceof DefaultAutoHandledException) {
				defaultAutoHandledException = (DefaultAutoHandledException) e;
			} else {
				defaultAutoHandledException = new DefaultAutoHandledException(DefaultDescription.UNKNOWN_EXCEPTION, e);
			}

			//获取异常所对应的错误码、错误提示信息以及log用信息
			DescriptionGroup descriptionGroup = ResponseDefinitionFactory
					.getDescriptionGroup(defaultAutoHandledException);
			
			//log用信息直接输出到log
			if(defaultAutoHandledException.getResponseObject()!=null){
				LOG.error("[HandlerExceptionResolver] responseError: " + JSON.toJSONString(defaultAutoHandledException.getResponseObject()));
			}
			LOG.error("[HandlerExceptionResolver] " + descriptionGroup.getSystemLog(),e);

			Map<String, Object> attributes = new HashMap<String, Object>();
			
			//错误码和提示信息转化为json格式发到前端
			attributes.put("messageCode", descriptionGroup.getMessageCode());// 某些只有描述的异常，带上错误码
			attributes.put("messageDescription", descriptionGroup.getUserhint());
			attributes.put("responseError",defaultAutoHandledException.getResponseObject());
			
			String jsonRes =  JSONArray.toJSONString(attributes);

			String keyJssonStr = null;
			
			if (!urlOut.contains(url)) {
				// 把json字符串进行加密 传给客户端
				keyJssonStr = RSAUtil.encrypt(jsonRes, key);
			} else {
				keyJssonStr = jsonRes;
			}

			response.setContentType("text/html");
			response.setCharacterEncoding("UTF-8");
			response.getWriter().write(keyJssonStr);
		
	}
	
	/**
	 * 封装Exception
	 * @param baseOperationResponse
	 * @param urlOut
	 * @param url
	 * @param response
	 * @param key
	 * @throws Exception
	 * @since 0.0.1
	 */
	private void throwAutoExption(BaseOperationResponse<String> baseOperationResponse,String urlOut,String url,HttpServletResponse response,Key key) throws Exception{
		String jsonRes =  JSONArray.toJSONString(baseOperationResponse);
		
		String keyJssonStr = null;
		if (!urlOut.contains(url)) {
			// 把json字符串进行加密 传给客户端
			keyJssonStr = RSAUtil.encrypt(jsonRes, key);
		} else {
			keyJssonStr = jsonRes;
		}
		response.setContentType("text/html");
		response.setCharacterEncoding("UTF-8");
		response.getWriter().write(keyJssonStr);
	}
}
